//
// Created by ych on 2/14/24.
//
#include "hash.h"
#include "stdint.h"

const int mask = ~(1 << (sizeof(int) * 8 - 1));

void initVarHashMap(VarHashMap *hash_map) {
    hash_map->table = malloc(sizeof(VarHashNode *) * HASH_SIZE);
    hash_map->size = 0;
    hash_map->capacity = HASH_SIZE;
    hash_map->threshold = HASH_SIZE * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    memset(hash_map->table, 0, sizeof(VarHashNode *) * HASH_SIZE);
}

void insertVarHashMap(VarHashMap *hash_map, char *string, Uint p) {
    VarHashNode *new_node = malloc(sizeof(VarHashNode));
    if (new_node == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    new_node->hash = hashString(string);
    int index = new_node->hash & (hash_map->capacity - 1) & mask;
    new_node->key = strdup(string);
    new_node->value = p;
    new_node->next = hash_map->table[index];
    hash_map->table[index] = new_node;
    hash_map->size++;
    if (hash_map->size > hash_map->threshold) {
        extendVarHashMap(hash_map);//TODO HASHMAP extend
    }
}

int lookUpVarHashMap(VarHashMap *hash_map, char *string, VarHashNode **node_p) {
    int index = hashString(string) & (hash_map->capacity - 1) & mask;
    VarHashNode *current = hash_map->table[index];
    while (current != NULL) {
        if (strcmp(current->key, string) == 0) {
            *node_p = current;
            return True;
        }
        current = current->next;
    }
    *node_p = hash_map->table[index];
    return False;
}

void deleteKeyVarHashMap(VarHashMap *hash_map, char *string) {
    int index = hashString(string) & (hash_map->capacity - 1) & mask;
    VarHashNode *current = hash_map->table[index];
    if (strcmp(current->key, string) == 0) {
        hash_map->table[index] = current->next;
        free(current->key);
        free(current);
        hash_map->size--;
        return;
    }
    VarHashNode *pre = current;
    current = pre->next;
    while (current != NULL) {
        if (strcmp(current->key, string) == 0) {
            pre->next = current->next;
            free(current);
            hash_map->size--;
            return;
        }
        pre = current;
        current = pre->next;
    }
}

void freeVarHashMap(VarHashMap *hash_map) {
    for (int i = hash_map->capacity - 1; i >= 0; i--) {
        VarHashNode *current = hash_map->table[i];
        while (current != NULL) {
            VarHashNode *temp = current;
            current = current->next;
            free(temp->key);
            free(temp);
        }
    }
    free(hash_map->table);
}

void clearVarHashMap(VarHashMap *hash_map) {
    for (int i = hash_map->capacity - 1; i >= 0; i--) {
        VarHashNode *current = hash_map->table[i];
        while (current != NULL) {
            VarHashNode *temp = current;
            current = current->next;
            free(temp->key);
            free(temp);
        }
        hash_map->table[i] = NULL;
    }
}

static void insertVarHashMapA(VarHashNode **node_head, VarHashNode *node) {
    node->next = (*node_head);
    *node_head = node;
}

void extendVarHashMap(VarHashMap *hash_map) {
    VarHashNode **temp_table = hash_map->table;
    int origin_capacity = hash_map->capacity;
    hash_map->capacity <<= 1;
    hash_map->threshold = hash_map->capacity * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    hash_map->table = malloc(sizeof(VarHashNode *) * hash_map->capacity);
    memset(hash_map->table, 0, sizeof(VarHashNode *) * hash_map->capacity);
    if (hash_map->table == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    for (int i = origin_capacity - 1; i >= 0; i--) {
        VarHashNode *current = temp_table[i];
        while (current != NULL) {
            VarHashNode *temp = current;
            current = current->next;
            int index = temp->hash & (hash_map->capacity - 1) & mask;
            insertVarHashMapA(hash_map->table + index, temp);
        }
    }
    free(temp_table);
}

void initIntHashMap(IntHashMap *hash_map) {
    hash_map->table = malloc(sizeof(IntHashNode *) * HASH_SIZE);
    hash_map->size = 0;
    hash_map->capacity = HASH_SIZE;
    hash_map->threshold = HASH_SIZE * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    memset(hash_map->table, 0, sizeof(IntHashNode *) * HASH_SIZE);
}

void insertIntHashMap(IntHashMap *hash_map, Int i, Uint p) {
    IntHashNode *new_node = malloc(sizeof(IntHashNode));
    if (new_node == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    new_node->hash = hashInt(i);
    int index = new_node->hash & (hash_map->capacity - 1) & mask;
    new_node->key = i;
    new_node->value = p;
    new_node->next = hash_map->table[index];
    hash_map->table[index] = new_node;
    hash_map->size++;
    if (hash_map->size > hash_map->threshold) {
        extendIntHashMap(hash_map);
    }
}

int lookUpIntHashMap(IntHashMap *hash_map, Int i, IntHashNode **node_p) {
    int index = hashInt(i) & (hash_map->capacity - 1) & mask;
    IntHashNode *current = hash_map->table[index];
    while (current != NULL) {
        if (current->key == i) {
            *node_p = current;
            return True;
        }
        current = current->next;
    }
    *node_p = hash_map->table[index];
    return False;
}

void freeIntHashMap(IntHashMap *hash_map) {
    for (int i = hash_map->capacity - 1; i >= 0; i--) {
        IntHashNode *current = hash_map->table[i];
        while (current != NULL) {
            IntHashNode *temp = current;
            current = current->next;
            free(temp);
        }
    }
    free(hash_map->table);
}

static void insertIntHashMapA(IntHashNode **node_head, IntHashNode *node) {
    node->next = (*node_head);
    *node_head = node;
}

void extendIntHashMap(IntHashMap *hash_map) {
    IntHashNode **temp_table = hash_map->table;
    int origin_capacity = hash_map->capacity;
    hash_map->capacity <<= 1;
    hash_map->threshold = hash_map->capacity * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    hash_map->table = malloc(sizeof(IntHashNode *) * hash_map->capacity);
    memset(hash_map->table, 0, sizeof(IntHashNode *) * hash_map->capacity);
    if (hash_map->table == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    for (int i = origin_capacity - 1; i >= 0; i--) {
        IntHashNode *current = temp_table[i];
        while (current != NULL) {
            IntHashNode *temp = current;
            current = current->next;
            int index = temp->hash & (hash_map->capacity - 1) & mask;
            insertIntHashMapA(hash_map->table + index, temp);
        }
    }
    free(temp_table);
}

void initFloatHashMap(FloatHashMap *hash_map) {
    hash_map->table = malloc(sizeof(FloatHashNode *) * HASH_SIZE);
    hash_map->size = 0;
    hash_map->capacity = HASH_SIZE;
    hash_map->threshold = HASH_SIZE * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    memset(hash_map->table, 0, sizeof(FloatHashNode *) * HASH_SIZE);
}

void insertFloatHashMap(FloatHashMap *hash_map, Float f, Uint p) {
    FloatHashNode *new_node = malloc(sizeof(FloatHashNode));
    if (new_node == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    new_node->hash = hashFloat(f);
    int index = new_node->hash & (hash_map->capacity - 1) & mask;
    new_node->key = f;
    new_node->value = p;
    new_node->next = hash_map->table[index];
    hash_map->table[index] = new_node;
    hash_map->size++;
    if (hash_map->size > hash_map->threshold) {
        extendFloatHashMap(hash_map);
    }
}

int lookUpFloatHashMap(FloatHashMap *hash_map, Float f, FloatHashNode **node_p) {
    int index = hashFloat(f) & (hash_map->capacity - 1) & mask;
    FloatHashNode *current = hash_map->table[index];
    while (current != NULL) {
        if (current->key == f) {
            *node_p = current;
            return True;
        }
        current = current->next;
    }
    *node_p = hash_map->table[index];
    return False;
}

void freeFloatHashMap(FloatHashMap *hash_map) {
    for (int i = hash_map->capacity - 1; i >= 0; i--) {
        FloatHashNode *current = hash_map->table[i];
        while (current != NULL) {
            FloatHashNode *temp = current;
            current = current->next;
            free(temp);
        }
    }
    free(hash_map->table);
}

static void insertFloatHashMapA(FloatHashNode **node_head, FloatHashNode *node) {
    node->next = (*node_head);
    *node_head = node;
}

void extendFloatHashMap(FloatHashMap *hash_map) {
    FloatHashNode **temp_table = hash_map->table;
    int origin_capacity = hash_map->capacity;
    hash_map->capacity <<= 1;
    hash_map->threshold = hash_map->capacity * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    hash_map->table = malloc(sizeof(FloatHashNode *) * hash_map->capacity);
    memset(hash_map->table, 0, sizeof(FloatHashNode *) * hash_map->capacity);
    if (hash_map->table == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    for (int i = origin_capacity - 1; i >= 0; i--) {
        FloatHashNode *current = temp_table[i];
        while (current != NULL) {
            FloatHashNode *temp = current;
            current = current->next;
            int index = temp->hash & (hash_map->capacity - 1) & mask;
            insertFloatHashMapA(hash_map->table + index, temp);
        }
    }
    free(temp_table);
}

void initStringHashMap(StringHashMap *hash_map) {
    hash_map->table = malloc(sizeof(StringHashNode *) * HASH_SIZE);
    hash_map->size = 0;
    hash_map->capacity = HASH_SIZE;
    hash_map->threshold = HASH_SIZE * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    memset(hash_map->table, 0, sizeof(StringHashNode *) * HASH_SIZE);
}

void insertStringHashMap(StringHashMap *hash_map, char *string, Uint p) {
    StringHashNode *new_node = malloc(sizeof(StringHashNode));
    if (new_node == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    new_node->hash = hashString(string);
    int index = new_node->hash & (hash_map->capacity - 1) & mask;;
    new_node->key = strdup(string);
    new_node->value = p;
    new_node->next = hash_map->table[index];
    hash_map->table[index] = new_node;
    hash_map->size++;
    if (hash_map->size > hash_map->threshold) {
        extendStringHashMap(hash_map);
    }
}

int lookUpStringHashMap(StringHashMap *hash_map, char *string, StringHashNode **node_p) {
    int index = hashString(string) & (hash_map->capacity - 1) & mask;
    StringHashNode *current = hash_map->table[index];
    while (current != NULL) {
        if (strcmp(current->key, string) == 0) {
            *node_p = current;
            return True;
        }
        current = current->next;
    }
    *node_p = hash_map->table[index];
    return False;
}

void freeStringHashMap(StringHashMap *hash_map) {
    for (int i = hash_map->capacity - 1; i >= 0; i--) {
        StringHashNode *current = hash_map->table[i];
        while (current != NULL) {
            StringHashNode *temp = current;
            current = current->next;
            free(temp->key);
            free(temp);
        }
    }
    free(hash_map->table);
}

static void insertStringHashMapA(StringHashNode **node_head, StringHashNode *node) {
    node->next = (*node_head);
    *node_head = node;
}

void extendStringHashMap(StringHashMap *hash_map) {
    StringHashNode **temp_table = hash_map->table;
    int origin_capacity = hash_map->capacity;
    hash_map->capacity <<= 1;
    hash_map->threshold = hash_map->capacity * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    hash_map->table = malloc(sizeof(StringHashNode *) * hash_map->capacity);
    memset(hash_map->table, 0, sizeof(StringHashNode *) * hash_map->capacity);
    if (hash_map->table == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    for (int i = origin_capacity - 1; i >= 0; i--) {
        StringHashNode *current = temp_table[i];
        while (current != NULL) {
            StringHashNode *temp = current;
            current = current->next;
            int index = temp->hash & (hash_map->capacity - 1) & mask;
            insertStringHashMapA(hash_map->table + index, temp);
        }
    }
    free(temp_table);
}

void initFuncHashMap(FuncHashMap *hash_map) {
    hash_map->table = malloc(sizeof(FuncHashNode *) * HASH_SIZE);
    hash_map->size = 0;
    hash_map->capacity = HASH_SIZE;
    hash_map->threshold = HASH_SIZE * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    memset(hash_map->table, 0, sizeof(FuncHashNode *) * HASH_SIZE);
}

FuncHashNode *insertFuncHashMap(FuncHashMap *hash_map, char *string, int func_params, int var_nums, Uint func_p) {
    FuncHashNode *new_node = malloc(sizeof(FuncHashNode));
    if (new_node == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    new_node->hash = hashString(string);
    int index = new_node->hash & (hash_map->capacity - 1) & mask;;
    new_node->key = strdup(string);
    new_node->value.func_params = func_params, new_node->value.var_nums = var_nums, new_node->value.func_p = func_p;
    new_node->next = hash_map->table[index];
    hash_map->table[index] = new_node;
    hash_map->size++;
    if (hash_map->size > hash_map->threshold) {
        extendFuncHashMap(hash_map);
    }
    return new_node;
}

int lookUpFuncHashMap(FuncHashMap *hash_map, char *string, FuncHashNode **node_p) {
    int index = hashString(string) & (hash_map->capacity - 1) & mask;
    FuncHashNode *current = hash_map->table[index];
    while (current != NULL) {
        if (strcmp(current->key, string) == 0) {
            *node_p = current;
            return True;
        }
        current = current->next;
    }
    *node_p = hash_map->table[index];
    return False;
}

void deleteKeyFuncHashMap(FuncHashMap *hash_map, char *string) {
    int index = hashString(string) & (hash_map->capacity - 1) & mask;
    FuncHashNode *current = hash_map->table[index];
    if (strcmp(current->key, string) == 0) {
        hash_map->table[index] = current->next;
        free(current->key);
        free(current);
        hash_map->size--;
        return;
    }
    FuncHashNode *pre = current;
    current = pre->next;
    while (current != NULL) {
        if (strcmp(current->key, string) == 0) {
            pre->next = current->next;
            free(current);
            hash_map->size--;
            return;
        }
        pre = current;
        current = pre->next;
    }
}

void freeFuncHashMap(FuncHashMap *hash_map) {
    for (int i = hash_map->capacity - 1; i >= 0; i--) {
        FuncHashNode *current = hash_map->table[i];
        while (current != NULL) {
            FuncHashNode *temp = current;
            current = current->next;
            free(temp->key);
            free(temp);
        }
    }
    free(hash_map->table);
}

static void insertFuncHashMapA(FuncHashNode **node_head, FuncHashNode *node) {
    node->next = (*node_head);
    *node_head = node;
}

void extendFuncHashMap(FuncHashMap *hash_map) {
    FuncHashNode **temp_table = hash_map->table;
    int origin_capacity = hash_map->capacity;
    hash_map->capacity <<= 1;
    hash_map->threshold = hash_map->capacity * HASH_TABLE_LOAD_FACTOR_THRESHOLD;
    hash_map->table = malloc(sizeof(FuncHashNode *) * hash_map->capacity);
    memset(hash_map->table, 0, sizeof(FuncHashNode *) * hash_map->capacity);
    if (hash_map->table == NULL) {
        throw(EXCEPTION_MEMORY);
    }
    for (int i = origin_capacity - 1; i >= 0; i--) {
        FuncHashNode *current = temp_table[i];
        while (current != NULL) {
            FuncHashNode *temp = current;
            current = current->next;
            int index = temp->hash & (hash_map->capacity - 1) & mask;
            insertFuncHashMapA(hash_map->table + index, temp);
        }
    }
    free(temp_table);
}

int hashString(char *string) {
    int hash_value = 17;
    while (*string) {
        hash_value = ((hash_value << 5) + hash_value) + *string++;
    }
    return hash_value ^ (hash_value >> 16);
}

int hashInt(Int i) {
    return (int) (void *) (i ^ (i >> (sizeof(Int) * 4)));
}

int hashFloat(Float f) {
    Int bits = *(Int *) &f;
    return (int) (void *) ((bits >> (sizeof(Int) * 4)) ^ bits);
}